﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Diagnostics;
using System.Linq;
using System.ServiceProcess;
using System.Text;
using System.Threading;
using System.Transactions;
using System.Configuration.Install;
using System.ServiceModel;
using System.Configuration;

namespace IsaControl
{
	public partial class IsaControlService : ServiceBase
	{
		public const bool coIsaLive = true;
		private const int coExpiryCheck = 60;		// seconds, check expiry this often
		public const int coMaxExpiry = 90;			// minutes, max allowed expiry time

		private volatile bool allowcheck;
		private ServiceHost serviceHost = null;

		public IsaControlService()
		{
			InitializeComponent();
		}

		protected override void OnStart(string[] args)
		{
			if (serviceHost != null)
				serviceHost.Close();

			serviceHost = new ServiceHost(typeof(WCFHandler));
			serviceHost.Open();


			allowcheck = true;

			Thread t = new Thread(ThreadExpiryCheck);
			t.IsBackground = true;
			t.Priority = ThreadPriority.Normal;
			t.Start();
		}

		protected override void OnStop()
		{
			allowcheck = false;
			if (serviceHost != null)
			{
				serviceHost.Close();
				serviceHost = null;
			}
		}

		private void ThreadExpiryCheck()
		{
			StatusConfirm();
			DateTime nextcheck = DateTime.Now;
			int count = 0;

			while (allowcheck)
			{
				Thread.Sleep(2000);
				if (DateTime.Now < nextcheck) continue;
				
				try
				{
					using (var trans = new TransactionScope(TransactionScopeOption.Required, new TransactionOptions { IsolationLevel = System.Transactions.IsolationLevel.Serializable }))
					{
						using (var con = new IsaDataClassesDataContext())
						{
							RuleControl control = null;
							try
							{
								DateTime now = DateTime.Now;
								var expired = con.RuleInfos.Where(r => r.isactive && r.autoclear <= now);

								foreach (var r in expired)
								{
									if (control == null)
										control = new RuleControl(coIsaLive);

									control.AlterRule(r.rulename, false);
									r.isactive = false;
									r.setby = null;
									r.whenset = null;
									r.autoclear = null;
								}
							}
							finally
							{
								if (control != null)
									control.Dispose();
							}

							con.SubmitChanges();
						}
						trans.Complete();
					}
				}
				catch
				{
					// ignore
				}

				++count;
				if (count > 120)
				{
					count = 0;
					StatusConfirm();
				}
				nextcheck = DateTime.Now.AddSeconds(coExpiryCheck);
			}
		}

		/// <summary>
		/// Check all rule status, and update tabel to reflect this (may be slow)
		/// </summary>
		public void StatusConfirm()
		{
			using (var control = new RuleControl(coIsaLive))
			{
				using (var trans = new TransactionScope(TransactionScopeOption.Required, new TransactionOptions { IsolationLevel = System.Transactions.IsolationLevel.Serializable }))
				{
					using (var con = new IsaDataClassesDataContext())
					{
						DateTime maxtime = DateTime.Now.AddMinutes(coMaxExpiry);

						foreach (var r in con.RuleInfos)
						{
							bool truestate = control.IsRuleEnabled(r.rulename);
							if (truestate != r.isactive)
							{
								// status in database is wrong
								r.isactive = truestate;
								if (!truestate)
								{
									r.setby = null;
									r.whenset = null;
									r.autoclear = null;
								}
								else
								{
									if (!r.autoclear.HasValue || r.autoclear > maxtime)
										r.autoclear = maxtime;
								}
							}
						}

						con.SubmitChanges();
					}
					trans.Complete();
				}
			}
		}
	}

	public class WCFHandler : IIsaInterface
	{
		/// <summary>
		/// WCF routine to check for pending requests
		/// </summary>
		public int DoPending()
		{
			if (!IsaControlService.coIsaLive) return 1;

			using (var trans = new TransactionScope(TransactionScopeOption.Required, new TransactionOptions { IsolationLevel = System.Transactions.IsolationLevel.Serializable }))
			{
				using (var con = new IsaDataClassesDataContext())
				{
					RuleControl control = null;
					try
					{
						foreach (var ch in con.PendingChanges)
						{
							if (control == null)
								control = new RuleControl(IsaControlService.coIsaLive);

							if (ch.newstate)
							{
								// request to set a rule
								if (ch.expires < DateTime.Now) continue;

								control.AlterRule(ch.RuleInfo.rulename, true);

								ch.RuleInfo.isactive = true;
								ch.RuleInfo.setby = ch.username;
								ch.RuleInfo.whenset = DateTime.Now;

								DateTime maxtime = DateTime.Now.AddMinutes(IsaControlService.coMaxExpiry);
								DateTime d;
								if (ch.expires > maxtime)
									d = maxtime;
								else
									d = ch.expires;
								ch.RuleInfo.autoclear = d;
							}
							else
							{
								// request to clear a rule
								control.AlterRule(ch.RuleInfo.rulename, false);

								ch.RuleInfo.isactive = false;
								ch.RuleInfo.setby = null;
								ch.RuleInfo.whenset = null;
								ch.RuleInfo.autoclear = null;
							}

							con.PendingChanges.DeleteOnSubmit(ch);
						}
					}
					finally
					{
						if (control != null)
							control.Dispose();
					}

					con.SubmitChanges();
				}
				trans.Complete();
			}
			return 0;
		}
	}

	[RunInstaller(true)]
	public class ProjectInstaller : Installer
	{
		private ServiceProcessInstaller process;
		private ServiceInstaller service;

		public ProjectInstaller()
		{
			process = new ServiceProcessInstaller();
			process.Account = ServiceAccount.LocalSystem;
			service = new ServiceInstaller();
			service.ServiceName = "ISA Control Tool";
			service.StartType = ServiceStartMode.Manual;
			Installers.Add(process);
			Installers.Add(service);
		}
	}

	[ServiceContract(Namespace = "http://www.bsfc.ac.uk/isacontrol")]
	public interface IIsaInterface
	{
		[OperationContract]
		int DoPending();
	}
}
